package com.shchekoldin.juliet.packager.plugins;

import com.shchekoldin.juliet.packager.core.Packager;
import com.shchekoldin.juliet.packager.utils.FileContentReader;
import com.shchekoldin.juliet.packager.utils.FilesFinder;
import com.shchekoldin.juliet.packager.utils.FilesIterator;
import com.shchekoldin.juliet.packager.utils.HashGenerator;
import com.shchekoldin.juliet.packager.xml.GroupElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;

import java.io.*;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PagesPreprocessor implements PackagerPlugin {

    private enum PageElementType {
        JS,
        CSS
    }

    private static final Logger logger = LoggerFactory.getLogger(PagesPreprocessor.class);

    private static int generatedGroupsCount = 0;

    private FilesIterator filesIterator;

    private String replaceToPath;

    // -------------------------------------------------------
    // -                        LOGIC                        -
    // -------------------------------------------------------

    private String generateGroupName(String pagePath, String pageBasePath, PageElementType elementType) {
        ++generatedGroupsCount;
        return HashGenerator.generateMd5(pageBasePath + pagePath + elementType + generatedGroupsCount)
            + "-" + generatedGroupsCount;
    }

    private void copyFile(File sourceFile, File destFile) throws IOException {
        destFile.createNewFile();

        FileChannel source = null;
        FileChannel destination = null;

        try {
            source = new FileInputStream(sourceFile).getChannel();
            destination = new FileOutputStream(destFile).getChannel();
            destination.transferFrom(source, 0, source.size());
        } finally {
            if (source != null) source.close();
            if (destination != null) destination.close();
        }
    }

    private void savePageContent(String content, String filePath) {
        try {
            FileOutputStream fileStream = new FileOutputStream(filePath);
            OutputStreamWriter fileWriter = new OutputStreamWriter(fileStream, "utf-8");

            fileWriter.append(content);
            fileWriter.close();
        } catch (IOException e) {
            logger.error("Can't save page", e);
            Packager.getInstance().exit();
        }
    }

    private String replaceLastMatch(Matcher matcher, String replacement) {
        StringBuffer result = new StringBuffer();
        matcher.appendReplacement(result, replacement);
        matcher.appendTail(result);
        return result.toString();
    }

    private String preprocessPageJavaScript(Matcher matcher, String groupName, String postfix, String groupContent) {
        Pattern subPattern = Pattern.compile("src=\"(.*?)\"", Pattern.DOTALL);
        Matcher subMatcher = subPattern.matcher(groupContent);
        GroupElement newGroup = new GroupElement(groupName, filesIterator.getGroup());

        while (subMatcher.find()) {
            newGroup.getIncludeFiles().add(subMatcher.group(1));
        }

        Packager.getInstance().addNewGroup(newGroup);

        return replaceLastMatch(matcher, "<script type=\"text/javascript\" src=\""
                + this.replaceToPath + groupName + postfix + ".js\" charset=\"utf-8\"></script>");
    }

    private String preprocessPageCss(Matcher matcher, String groupName, String postfix, String groupContent) {
        Pattern subPattern = Pattern.compile("href=\"(.*?)\"", Pattern.DOTALL);
        Matcher subMatcher = subPattern.matcher(groupContent);
        GroupElement newGroup = new GroupElement(groupName, filesIterator.getGroup());

        while (subMatcher.find()) {
            newGroup.getIncludeFiles().add(subMatcher.group(1));
        }

        Packager.getInstance().addNewGroup(newGroup);

        return replaceLastMatch(matcher, "<link rel=\"stylesheet\" type=\"text/css\" href=\""
                + this.replaceToPath + groupName + postfix + ".css\" media=\"all\" />");
    }

    private void preprocessPage(String pagePath, String pageBasePath, PageElementType elementType)
        throws IOException {
        String pageContent = FileContentReader.getInstance().run(pageBasePath + pagePath);
        Pattern pattern = Pattern.compile("<!--\\s*juliet\\.packager\\.((([^.]+)\\.)?)start\\s*-->(.*?)"
            + "<!--\\s*juliet\\.packager\\.\\1end\\s*-->", Pattern.DOTALL);
        Matcher matcher = pattern.matcher(pageContent);

        while (matcher.find()) {
            String groupName = matcher.group(3);
            boolean pageContentModified = false;

            if (groupName == null) {
                groupName = generateGroupName(pagePath, pageBasePath, elementType);
            }

            String groupContent = matcher.group(4);
            Element optimizeOption = filesIterator.getGroup().getCustomOption("optimize");
            String postfix = optimizeOption.getAttribute("postfix");

            if ((elementType == PageElementType.JS) && (groupContent.indexOf("<script") != -1)) {
                pageContent = preprocessPageJavaScript(matcher, groupName, postfix, groupContent);
                pageContentModified = true;
            } else if ((elementType == PageElementType.CSS) && (groupContent.indexOf("<link") != -1)) {
                pageContent = preprocessPageCss(matcher, groupName, postfix, groupContent);
                pageContentModified = true;
            }

            if (pageContentModified) {
                matcher = pattern.matcher(pageContent);
            }
        }

        savePageContent(pageContent, pageBasePath + pagePath);
    }

    private void preprocess(List<String> pagesPaths, String pageBasePath, PageElementType elementType) {
        for (String pagePath : pagesPaths) {
            try {
                preprocessPage(pagePath, pageBasePath, elementType);
            } catch (IOException e) {
                logger.error("Can't read page", e);
                Packager.getInstance().exit();
            }
        }
    }

    //
    // API
    //

    @Override
    public void initialize(FilesIterator filesIterator) {
        GroupElement group = filesIterator.getGroup();
        // Get page option only from current group. Don't use recursive search!
        Element pageOption = group.getCustomOption("page", false);

        if (pageOption == null) return;

        Element replaceToPathOption = group.getCustomOption("replace-to-path");

        this.replaceToPath = replaceToPathOption.getTextContent();
        this.filesIterator = filesIterator;

        // Prevent page's groups processing.
        filesIterator.interrupt();

        FilesFinder filesFinder = FilesFinder.getInstance();
        String pageBasePath = pageOption.getAttribute("basePath");
        String pagePath = pageOption.getTextContent();
        List<String> pagesPaths;

        if (filesFinder.isRegexpPath(pagePath)) {
            pagesPaths = FilesFinder.getInstance().findFiles(pagePath, pageBasePath);
        } else {
            pagesPaths = new ArrayList<String>();
            pagesPaths.add(pagePath);
        }

        PageElementType elementType = PageElementType.valueOf(pageOption.getAttribute("type").toUpperCase());
        preprocess(pagesPaths, pageBasePath, elementType);
    }

}
